
はじめに
こんにちは!株式会社エブリーで1ヶ月間、iOS エンジニアとしてインターンをしていた木村風葉です。ヘルシカの iOS チームで、食事記録まわりの改善や無料会員向けの機能追加など、いくつかの機能開発に取り組みました。
この記事では、インターンの後半に取り組んだ「Jev」という新しいAIを使った検証ツールづくりについて書きます。Jevは、文章を生成する代わりに「選択肢のどれを選ぶか」を確率付きで返すAIです。これを使ってiOSシミュレータ上のアプリを自動で触り回らせ、不具合を探す仕組みを作りました。
なお、ヘルシカの開発チームでは、AI コーディングツールの Claude Code を日常的に使っています。この記事で Jev と比べている「Claude」は、基本的にこの Claude Code を指します。
Jev とは
Jev は、TypeSafe AI が開発した「System One Model」と呼ばれる AI モデルです。ChatGPT や Claude のような LLM(大規模言語モデル)は文章を生成しますが、Jev は文章を生成しません。状態(state)と、型の決まった質問(questions)を渡すと、それぞれの質問に対する答えを確率付きで返します。
質問の型は次の3つです。
| 型 | 返ってくるもの | 例 |
|---|---|---|
noul |
真である確率(0〜1) | 「エラーが表示されているか」→ 0.93 |
choice |
選んだ選択肢と、全選択肢の確率 | 「次に押すボタンはどれか」→ 「続行(3/4)」 |
score |
段階評価と、各段階の確率 | 「健康的な度合い(悪い/普通/良い)」 |
たとえば、1日の食事内容を渡して「栄養バランスが取れているか」「一番の課題は何か」を聞くと、次のような形で答えが返ってきます(イメージです)。
{ "answers": { "is_balanced": { "noul": 0.12 }, "main_issue": { "choice": "veg", "confidence": 0.81, "probabilities": { "carb": 0.11, "fat": 0.08, "veg": 0.81 } } } }
主な特徴は次のとおりです。
- 速い: 1回の判断が数百ミリ秒程度で返ってきます
- 安い: 入力は 100万トークンあたり $0.042 で、出力は無料です(2026年10月時点)
- 入力はテキストだけ: 画像は渡せません
今回は、社内で検証用に一時的に用意していただいた環境を使って試しました。
作ったもの:jev-qa
きっかけ
検証用の環境を用意していただいて最初に思いついたのは、アプリの動作確認です。オンボーディング(初回の登録の流れ)を毎回手で進めるのが大変だったので、「Jev に画面を判断させながら、高速に操作させられないか」と考えました。
ただ、オンボーディングは画面の順番が決まっています。決まった手順を毎回 AI に判断させるより、手順を書いたテストスクリプト(XCUITest など)のほうが速くて確実です。そこで、目的を次のように広げました。
オンボーディングをjevで通せるなら、その先の画面も触らせて、不具合探しにも使えるのでは?
手順が決まっていない状態でアプリ中を触り回るのであれば、毎回その場で判断が必要になります。ここなら、速くて安い Jev の出番があるのではないかと考えました。
構成
作ったツールは jev-qa と名付けました。(今思えばヘルシカに限定した名前の方が良かったかも...)
主な構成は次のとおりです。
- AXe: iOS シミュレータの画面情報を読み取り、タップや入力を行う CLI ツール
- jev_qa.py: 画面の情報を Jev に渡し、返ってきた判断をもとに操作する Python スクリプト
- 設定ファイル(YAML): アプリ固有のルールや、検証したい操作の流れ(シナリオ)
1ステップの流れ
Jev には画像を渡せないので、スクリーンショットではなくアクセシビリティの情報(画面上のボタンや文字の情報をテキストにしたもの)を渡します。1ステップの流れは次のとおりです。

Jev には、1回の呼び出しで次の質問をまとめて聞いています。
| 質問 | 型 | 答え |
|---|---|---|
operation |
choice | タップ/入力/下にスクロール/上にスクロール/戻る/待つ/完了/行き詰まり |
target |
choice | 操作する要素の番号 |
is_error_shown |
noul | エラー表示が出ているか |
goal_achieved |
noul | ゴールを達成したか |
ここでハマったのが、Jev は質問ごとに別々に判断していて、ほかの質問の選択肢を見ていないという点です。最初は要素の一覧を target の選択肢にだけ入れていたため、operation を判断するときに画面の中身が分かっていませんでした。そのため、要素の一覧は状態(state)にも入れるようにしました。
Jev の判断をそのまま使わない
動かしているうちに、Jev の判断をそのまま使うと困る場面がいくつも出てきました。たとえば、同じボタンを押し続ける、自信が無いのに「戻る」を選ぶ、といった動きです。そこで、ツール側で次のような補正をかけるようにしました。
- 自信度が 0.7 未満の「戻る」は実行しない
- 画面が変わらなかったのに同じ要素を押そうとしたら、次に確率が高い要素にする
- 購入・削除・退会・サインインなどのボタンは押さない
- 課金画面(paywall)が出たら、Jev に判断させずに左上の × を押す
また、Jev が「ゴールを達成した」と答えても、それだけでは完了にしないようにしました。自己申告だけで完了にすると、失敗しているのに完了扱いになることが連発したためです。代わりに、シナリオの段階ごとに「この画面にこの文字が出ていたら達成」という条件(done_when)を書き、ツールが確認します。
シナリオの例(イメージ)
name: 体重を記録する start: relaunch: true # アプリを再起動してから始める screen: home inputs: ["60.5"] # 入力欄に入れてよい値 stages: - goal: 体重の記録画面を開く done_when: screen: weight_record max_steps: 6 - goal: 体重を 60.5 kg で保存する done_when: texts_all: ["60.5"] screen: home max_steps: 8
ゴールを段階に分けたのは、Jev が「この画面で次に何を押すか」を選ぶのは得意な一方、長い手順の中で「今どこにいて、何をすべきか」を判断するのは苦手だったためです。判断させる範囲を小さくするほど、動きが安定しました。
こうして、体重の記録、運動の記録、設定画面の行き来、日付の切り替えなど、いくつかのシナリオを通せるようになりました。

探索モード:ゴールを決めずに触り回る
決まったフローの確認よりも Jev が活きそうなのが、ゴールを決めずにアプリ中を触り回る探索です。秒数を指定すると、まだ押していないボタンを優先して押し続けるモードを作りました。
python3 jev_qa.py --explore 240 # 240秒、ゴールを決めずにアプリ中を触る
固まった、アプリの外に出た、完全に行き詰まった、といった場合は、アプリを再起動して続けます。最初の4分間の探索では、83ステップで65種類のボタンを触りました。
気になる点を「スクショの名前」で記録する
探索の目的の1つは、不具合や、ユーザーにとってわかりにくい表示を見つけることです。ただ、Jev は文章を出せないので、「何がおかしいか」を説明させることはできません。そこで、気になる点があったら、スクリーンショットを ISSUE_<種類>_step<番号>.png という名前で残すことにしました。何かあったことさえ分かれば、あとから人や Claude が見て判断できます。
| 種類 | 判定する方法 |
|---|---|
| エラー表示・わかりにくい表示 | Jev(noul の確率が高いとき) |
ボタンの読み上げ名が画像のファイル名になっている、仮の値(null など)が出ている |
スクリプト |
| 固まった・アプリの外に出た・行き詰まった | ツール |
ここでいう 「わかりにくい表示」 は、Jev に「画面に、ユーザーにとってわかりにくい・不自然な表示があるか」を noul で聞き、その確率が高かったときに記録するものです。質問文には、説明のない数値や記号、意味の通じない文言、不自然な空欄などを例として書いています。
「123456 の次は 123457」を試せる探索
探索を何回かに分けて流すと、毎回同じところばかり触ってしまいます。そこで、画面と操作の記録を、実行をまたいで残すようにしました。
最初は「一度押したボタンは次から避ける」という単純な仕組みにしました。しかし、それでは不十分でした。たとえば、ボタン 1→2→3→4→5→6 と進んだ後、6 の代わりに 7 を押すルート(1→2→3→4→5→7)も試してほしいのに、1 が「押した」扱いになると、その先の分岐を試せなくなります。
そこで、画面と遷移をグラフ(画面A --[押したボタン]--> 画面B)として記録し、次のルールで選ぶようにしました。

- その画面に、まだ押していないボタンがあれば、それを押す
- 全部押し済みなら、押した先に未探索の分岐が一番近く残っているボタンをもう一度押す
- 先が全部探索済みなら、戻るかスクロールする
途中のボタンは、その先がすべて探索済みになるまで「済み」にしません。これで、1→2→3→4→5→6 を試した後に、1〜5 を通り直して 7 を試す動きになりました。
比較と評価
「Claude がやったほうが早くない?」問題
シナリオが通るようになってきた一方で、運動の記録(ウォーキング30分を追加する)シナリオを動かしたとき、1回に47秒かかりました。オンボーディングのときより、1つ1つの操作が明らかに遅く感じました。
試しに、Jev を使わずに Claude に同じシナリオの操作をさせてみたところ、19.5秒で終わりました。正直、「これなら Jev を使う意味なくない?」と思いました。
まず、どこに時間がかかっているのかを測りました。
| Jev の判断 | 操作 | 画面の変化待ち | |
|---|---|---|---|
| 1ステップの平均 | 0.33秒 | 1.16秒 | 2.01秒 |
Jev の判断は1ステップあたり0.3秒ほどで、全体の1割程度でした。時間の大半は、画面の読み取りと操作、つまり Jev 以外の部分でかかっていました。
原因を調べると、AXe がタップのたびに画面の向きを確認していて約0.6秒かかることや、画面の読み取りが要素の数に比例して遅くなることが分かりました。入力の手順を整理したり、操作後の固定の待ち時間をやめて画面の変化を見て進むようにしたりして、運動の記録のシナリオは 47.1秒 → 約33秒 になりました。
また、Claude の19.5秒は、何度も動かして画面の流れやボタンの名前を把握したうえで、あらかじめ完璧な手順を書いたスクリプトを実行した時間でした。初めて見る画面で同じことはできません。Claude が1手ずつ画面を見て判断する場合は、1手ごとに数秒以上かかり、費用も Jev より桁違いに高額になります。
整理すると、次のようになります。
| やり方 | 速さ | 初めて見る画面 | 画面が変わったとき | 費用 |
|---|---|---|---|---|
| 決め打ちのスクリプト | ◎ | × 事前に作る必要がある | × 壊れる | ほぼ0 |
| Jev が1手ずつ判断 | ○ | ○ | ○ | ほぼ0 |
| Claude が1手ずつ判断 | △ | ◎ | ◎ | 高い |
ここから、決まった操作フローの確認なら決め打ちのスクリプトのほうがよく、Jev は手順が決まっていない「探索」に向いている、という方向性が見えてきました。
料金
Jev は入力が安く、出力が無料なので、料金はほとんどかかりませんでした(1ドル150円で計算)。
| 実行 | 入力トークン | 料金 |
|---|---|---|
| 1ステップ | 約1,500 | 約0.01円 |
| シナリオ1本(4〜9ステップ) | 7,500〜17,000 | 約0.05〜0.1円 |
| 探索 4分 | 12.5万〜14.5万 | 約0.8〜0.9円 |
4分間アプリを触り回って1円以内です。シナリオ6本を1日10回流しても、月に200円もかかりません。(もちろん1日10回回す必要はないですが...)
Claude と Jev の使い分け
最後に、Jev を使わずに Claude だけで2分間の探索をさせ、Jev と比べてみました。
| Claude が直接操作 | Jev による探索 | |
|---|---|---|
| 1操作あたり | 約7秒 | 約2〜3秒 |
| 2分で触れた数 | 15操作 | 40〜50操作 |
| 料金 | Claude の利用量 | 約0.5円 |
| 見つけやすいもの | 意味を伴う違和感(表示の順番、仕様と合っているか など) | エラー表示、固まる、行き詰まる |
この結果から、次のような使い分けがよいと考えています。
- 決まった操作フローの確認: 決め打ちのスクリプト(速くて確実)
- 広く速く触って、異常を見つける: Jev(速くて安い)
- 見つかったものの意味を判断する、原因を調べる、シナリオを書く: Claude
Jev は Claude の代わりになるものではなく、大量に、速く、安く判断したい部分を受け持つ部品だと感じました。
見つかったもの
数日間の試行で、手で触っていたときには気づいていなかった不具合や、仕様を確認したほうがよさそうな点がいくつか見つかりました。
見つかったものは、大きく次の3種類でした。
- 動作の不具合: アプリが応答しなくなる、エラーが表示されるといった、ユーザーの操作が止まってしまうもの
- 表示の違和感: 画面の状況と文言が合っていないなど、仕様として意図どおりかを確認したいもの
- アクセシビリティの問題: 画面の読み上げで、ボタンの役割が伝わりにくいもの
特に役に立ったのは、普段あまり通らない操作の組み合わせで起きるものを拾えたことです。探索モードは人が普段やらないようなパターンも網羅的に走査できるので、手での確認では通りにくい道筋を自然に通ってくれました。見つけたときにはスクリーンショットと操作の記録が残っているので、再現手順をすぐに確認でき、そのまま原因の調査につなげられました。
アクセシビリティの問題は、最初から探していたわけではなく、ツールを作っている途中で気づいたものです。このツールは画面を読むのにアクセシビリティの情報を使っていて、これは iPhone の読み上げ機能(VoiceOver)が読み上げる情報と同じです。つまり、ツールで読んだボタンの名前が画像のファイル名になっていれば、VoiceOver でもそのまま読み上げられているということです。ボタンに意味のある名前が付いていれば、Jev や XCUITest もボタンを見分けやすくなるので、テストのしやすさにもつながると感じました。
課題
失敗談:課金画面の購入ボタンを押しかけた
探索モードでは、ヒヤッとすることもありました。
探索中に課金画面(paywall)が開いたのですが、Webで表示される画面だったため中身の読み込みがツールの確認より遅れ、ツールが課金画面に気づけませんでした。ツールは後ろにあるホーム画面の情報をもとにボタンを押すため、その位置によっては課金画面の購入ボタンを押す可能性があります。 シミュレータはAppleアカウントにサインインしていなかったので実際に有料プランの購入を完了させることはありませんが、AIに操作させるときは「押してはいけないものを押す」前提で守りを固める必要がある、と実感しました。対策として、次の3つを入れました。
- 押す直前に、押す位置の要素が今の画面の情報に含まれているかを確かめる。含まれていなければ押さない
- サインイン画面が出たら、Jev に判断させず「キャンセル」だけを押す
- 探索モードでは、課金画面の入口になるボタンを押さない
「わかりにくい表示」の判定が当てにならない
Jev による「わかりにくい表示」の判定は誤検知が多く、正直なところあまり役に立ちませんでした。一方で、エラー表示の判定や、スクリプトでの判定は役に立ちました。表示の意味まで踏み込んだ判断は、今のところ Claude や人が見たほうが確実です。ツールの実行をClaudeに持たせればツールの検知した「わかりにくい表示」が誤検知かどうかQA後に判定してくれるので、そのような実行方法の方が振り返りやすいと思います。
まとめ
Jev を使って、iOS シミュレータ上のアプリを自動で触り回る検証ツールを作りました。
取り組みを通じて、次のことを学びました。
- AI に操作させるときの速さは、AI の判断以外で決まることが多い。推測せずに測ったら、Jev の判断は全体の1割程度でした
- AI に判断させる範囲は小さくする。長いゴールを段階に分けると、動きが安定しました
- AI の自己申告をそのまま信じない。完了の判定や、押してはいけないボタンは、ツール側のルールで決めます
- 道具ごとに得意なことが違う。決め打ちのスクリプト・Jev・Claude を役割で使い分けるのがよいと考えています
今後は、Jev がアプリを操作し、失敗したときは Claude が原因を調べて設定を直す、という仕組みも作ってみたいです。その際は、テストを通すために Claude が合格の条件を緩めてしまわないよう、Claude に直させてよい範囲を決めておく必要があります。
エブリーのインターンでは、複数の機能開発だけでなく、新しい技術を試して開発のやり方そのものを改善する取り組みにも挑戦できました。実際にチームに入り、新機能の実装からリリースまでの一連の流れに関われたのは、とても貴重な機会でした。
社員の皆さんは明るく接しやすい方ばかりで、気軽にランチにも誘っていただきました。おかげで、楽しく充実した1ヶ月を過ごせました。大変お世話になりました。
新しい技術を試しながらプロダクトの開発に関わりたい方は、ぜひエブリーのインターンに応募してみてください!
※28卒インターンの募集は終了しました







